'use strict';
/**
 * generate model
 *
 */
var path = require('path');
var util = require('util');
var yeoman = require('yeoman-generator');
var _ = require('underscore');
var q = require('q');
var chalk = require('chalk');
var ScriptBase = require('../../lib/script-base.js');
// database datatype
/*
Sequelize.STRING                      // VARCHAR(255)
Sequelize.STRING(1234)                // VARCHAR(1234)
Sequelize.STRING.BINARY               // VARCHAR BINARY
Sequelize.TEXT                        // TEXT

Sequelize.INTEGER                     // INTEGER
Sequelize.BIGINT                      // BIGINT
Sequelize.BIGINT(11)                  // BIGINT(11)

Sequelize.FLOAT                       // FLOAT
Sequelize.FLOAT(11)                   // FLOAT(11)
Sequelize.FLOAT(11, 12)               // FLOAT(11,12)

Sequelize.DOUBLE                      // DOUBLE
Sequelize.DOUBLE(11)                  // DOUBLE(11)
Sequelize.DOUBLE(11, 12)              // DOUBLE(11,12)

Sequelize.DECIMAL                     // DECIMAL
Sequelize.DECIMAL(10, 2)              // DECIMAL(10,2)

Sequelize.DATE                        // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
Sequelize.BOOLEAN                     // TINYINT(1)

Sequelize.UUID                        // UUID datatype for PostgreSQL and SQLite, CHAR(36) BINARY for MySQL (use defaultValue: Sequelize.UUIDV1 or Sequelize.UUIDV4 to make sequelize generate the ids automatically)

Sequelize.ENUM('value 1', 'value 2')  // An ENUM with allowed values 'value 1' and 'value 2'

Sequelize.ARRAY(Sequelize.TEXT)       // Defines an array. PostgreSQL only.

Sequelize.JSON                        // JSON column. PostgreSQL only.
Sequelize.JSONB                       // JSONB column. PostgreSQL only.

Sequelize.REAL                        // REAL        PostgreSQL only.
Sequelize.REAL(11)                    // REAL(11)    PostgreSQL only.
Sequelize.REAL(11, 12)                // REAL(11,12) PostgreSQL only.

Sequelize.BLOB                        // BLOB (bytea for PostgreSQL)
Sequelize.BLOB('tiny')                // TINYBLOB (bytea for PostgreSQL. Other options are medium and long)
*/


var choices = {
    'RDBMS': [
        {name: 'String'   , value: 'STRING'  , canDef: true , ex: 'STRING(32)' , input: 1},
        {name: 'Text'     , value: 'TEXT'},
        {name: 'Interger' , value: 'INTEGER'},
        {name: 'BIGINT'   , value: 'BIGINT'  , canDef: true , ex: 'BIGINT(11)' , input: 1},
        {name: 'FLOAT'    , value: 'FLOAT'   , canDef: true , ex: 'FLOAT(11) or FLOAT(11,12)' , input: 2},
        {name: 'DOUBLE'   , value: 'DOUBLE'  , canDef: true , ex: 'DOUBLE(11) or DOUBLE(11,12)' , input: 2},
        {name: 'DECIMAL'  , value: 'DECIMAL' , canDef: true , ex: 'DECIMAL(10 , 2)' , input: 1},
        {name: 'Date'     , value: 'DATE'},
        {name: 'Boolean'  , value: 'BOOLEAN'},
        {name: 'ENUM'     , value: 'ENUM' , canDef: true , ex: 'NUM(value 1 , value 2 , value 3)' , input: 'n'},
        {name: 'UUID'     , value: 'UUID' , canDef: true , defaults: ['Sequelize.UUIDV1' , 'Sequelize.UUIDV4']},
        {name: 'BLOB'     , value: 'BLOB' , canDef: true , input: ['tiny' , 'medium' , 'long']}
    ],
    'NOSQL': [
        {name: 'String'   , value: 'String'},
        {name: 'Number'   , value: 'Number'},
        {name: 'Boolean'  , value: 'Boolean'},
        {name: 'Array'    , value: 'Array'},
        {name: 'Buffer'   , value: 'Buffer'},
        {name: 'Date'     , value: 'Date'},
        {name: 'ObjectId' , value: 'ObjectId'},
        {name: 'Mixed'    , value: 'Mixed'}
    ],
    'NEWSQL': []
};
// this are postgresql only options
var PostgresqlOnly = [
        {name: 'ARRAY' , value: 'ARRAY' , defaults: 'Sequelize.TEXT'},
        {name: 'JSON'  , value: 'JSON' },
        {name: 'JSONB' , value: 'JSONB'},
        {name: 'REAL'  , value: 'REAL' , canDef: true , ex: 'REAL(11) or REAL(11,12)' , input: 2}
    ];

var Generator = module.exports = function()
{
    ScriptBase.apply(this, arguments);

    // then we add .ser.js to the end
    if (!this.env.options.appPath) {
        this.env.options.appPath = path.join('.','app','server','controllers');
    }
    this.scriptSuffix = '.js';
    // database setup always use interactive mode

    var config = this.getPanesConfig();

    if (!config) {
        var err = (this.lang==='cn') ? '' : 'Your installation might have been corrupted. Can not retrieve your original setup!';
        this.log.error(err);
        throw err;
    }
    else {
        this.lang = config['lang'];
    }

    this.databaseType = config['databaseType'];
    this.databaseDriver = config['databaseDriver'];
};

util.inherits(Generator, ScriptBase);

/**
 * prepare all the variables for use later
 */
Generator.prototype.generateOptions = function()
{
    // use this to hold all the data
    this.fields = {};
    //
    this.choices = choices[this.databaseType];
    // note that when the user choose NewSQL using SequoriaDB - its using a Postgresql front to bridge so it will be RDBMS
    // if they are using MyCat then it will be the other way around nosql --> rdbms bridge so we need to check which one
    if (this.databaseType==='NEWSQL') {
        switch (this.databaseDriver) {
            case 'sequoriadb':
                this.choices = choices['RDBMS'];
                // using postgresql!
                this.choices.concat(PostgresqlOnly);
            break;
            case 'mycat':
                this.choices = choices['NOSQL'];
            break;
            default:
                // nothing to do!
        }
    }
    else if (this.databaseType==='RDBMS') {
        if (this.databaseDriver==='postgresql') {
            this.choices = this.choices.concat(PostgresqlOnly);
            var idx = _.findIndex(this.choices , function(value)
            {
                if (value.name==='BLOB') {
                    return true;
                }
            });
            this.choices[idx]['defaults'] = ''; // postgresql use byte number
        }
    }
};

/**
 * get what type of database they were using
 * then fetch the definition files, let the user to add them one by one (just like cake did)
 */
Generator.prototype.askForSchema = function()
{
    this._askForFieldName();
};
/**
 * ask for the name of the field
 */
Generator.prototype._askForFieldName = function()
{
    var _this = this;
    var cb = this.async();
    var lang = this.lang;
    this.prompt({
        type: 'input',
        name: 'fieldName',
        message: (lang==='cn') ? '数据表列名稱:' : 'Name of the field'
    } , function(props)
    {
        cb();
        if (props.fieldName) {
            _this._defineData(props.fieldName);
        }
        else {
            _this.log.error((lang==='cn') ? '必需要提交表列名稱!' : 'You must provide a field name!');
            _this.askForSchema();
        }
    }.bind(this));
};

/**
 * actual body for defining the data type
 */
Generator.prototype._defineData = function(name)
{
    var _this = this;
    // generate the select
    _this.__defineSelectValue({
        message: (_this.lang==='cn') ? '选项数据类型' : 'Select the data type',
        choices: _this.choices,
        name: name,
        default: 'STRING'
    } , function(name , obj)
    {
        _this.fields[name] = obj;
        // add a duplicated field to make it easier to understand later
        _this.fields[name].dataType = obj.value;
        // special case
        if (obj.value==='ENUM') { // got to finish this one
            return _this.__defineNInput({
                name: name,
                message: (_this.lang==='cn') ? '请提供数组值' : 'Please provide array values'
            });
        }
        else {
            // exit or continue
            return _this.__next();
        }
    });
};

/**
 * @TODO ask if they want to define a default value
 */
Generator.prototype._askIfWantToDefineDefault = function()
{
    var _this = this;
    var cb = _this.async();
    this.prompt({
        type: 'confirm',
        name: 'defineDefault',
        message: (this.lang==='cn') ? '你要为' + name + '设置默认数据吗?' : 'Would you like to setup default value for ' + name + '?'
    } , function(props)
    {
        cb();
        if (props.defineDefault) {

        }
        else {

        }
    }.bind(this));
};

/**
 * @TODO this is going to further define the field value
 * not going to do too complicate now, only deal with the 1 or n input
 */
Generator.prototype.__generateFieldDef = function(name, obj)
{
    var _this = this;
    if (_.isArray(obj.input)) {
        _this.__generateSelectDefaults(name , {
            choices: obj.input,
            message: (lang==='cn') ? '请选项' : 'Please select one',
        }, function(value) {
            _this.fields[name].typeDef = value;
            _this.__next();
        });
    }
    else if (obj.input==='n') {
        _this.__defineNInput(name , obj);
    }
    else {
        _this.__defineInputValue(
            (lang==='cn') ? '请提供数字值' : 'Please supply a value (number only)',
            function(value) {
                value = parseInt(value);
                if (isNaN(value)) {
                    _this.log.error((lang==='cn') ? '只限数字!' : 'NUMBER ONLY!');
                    return _this.__generateFieldDef(name , obj);
                }
                _this.field[name].typeDef = value;
                _this.__next();
            }
        )
    }
};

/**
 * breaking it out so it won't get too long
 */
Generator.prototype.__generateSelectDefaults = function(name , obj)
{
    var _this = this;
    if (_.isArray(obj.defaults)) {
        _this.__defineSelectValue({
            choices: obj.defaults ,
            message: (lang==='cn') ? '请选项' : 'Please select one',
            default: obj.defaults[0]},
            function(selected) {
                _this.fields[name].default = selected;
                _this.__next();
            });
    }
    else {
        _this.fields[name].default = obj.defaults;
        _this.__next();
    }
};

/**
 * define default values, make this a re-usable call
 */
Generator.prototype.__defineSelectValue = function(opt, callback , exitCallback)
{
    var _this = this;
    var cb = _this.async();
    var getData = function(value)
    {
        return _.find(opt.choices , function(obj)
        {
            if (obj.value) {
                return obj.value === value;
            }
            else {
                return obj === value;
            }
        });
    };

    this.prompt({
        type: 'list',
        name: 'selected',
        choices: opt.choices,
        message: opt.message,
        default: opt.default
    } , function(props) {
        cb();
        if (exitCallback) {
            exitCallback(opt , callback);
        }
        else {
            callback( opt.name , getData(props.selected) );
        }
    });
};

/**
 * define a input value, create a re-usable input prompt
 */
Generator.prototype.__defineInputValue = function(opt , callback)
{
    var _this = this;
    var cb = _this.async();
    this.prompt({
        type: 'input',
        name: 'input',
        message: opt.message
    } , function(props) {
        cb();
        callback(opt.name , props.input);
    }.bind(this));
};

/**
 * define a recursive input
 */
Generator.prototype.__defineNInput = function(opt)
{
    var _this = this;
    var name = opt.name;
    _this.__defineInputValue(
        opt,
        function(name , value) {
            if (!_this.fields[name].values) {
                _this.fields[name].values = [];
            }
            _this.fields[name].values.push(value);
            // continue
            _this._askIfWantToExit({cn: '完成' + name + '请提供数字值?' , en: 'Finish providing value for ' + name + '?'}).then(function(finish)
            {
                if (finish) {
                    return _this.__next();
                }
                _this.__defineNInput(opt);
            });
        }
    )
};

/**
 * stock next action
 */
Generator.prototype.__next = function()
{
    var _this = this;
    _this._askIfWantToExit().then(function(finish)
    {
        if (!finish) {
            return _this.askForSchema();
        }
        _this._createModelFiles();
    });
};

/**
 * generate the model file
 */
Generator.prototype._createModelFiles = function()
{
    try {
        var schema = [];
        var _this = this;
        var databaseType = _this.databaseType.toLowerCase();
        _.each(this.fields , function(def , name)
        {
            var s = '';
            s += '\t\'' + name + '\':{\n';
            if (databaseType==='rdbms') {
                // our model now has a unify data type interface
                s += '\t\ttype: dt.' + def.dataType;
                // s += '\t\ttype: model.Sequelize.' + def.dataType;
                // if default , if values ...
                if (def.values) {
                    s += ',\n\t\tvalues: [\'' + def.values.join('\'','\'') + '\']\n';
                }
                else {
                    s += '\n';
                }
            }
            else if (databaseType==='nosql') {
                /**
                @TODO since crouchDB don't use a Schema, but we could create our own but it just useless.
                **/
                s += '\t\ttype: ' + def.dataType + '\n';
                // s += '\t\ttype: mongoose.Schema.Types.' + def.dataType + '\n';
            }
            s += '\t}';
            schema.push(s);
        });

        this.schema = schema.join(',\n');
        // need to set the root path just before we call it, otherwise it will get mess up
        this.sourceRoot(path.join(__dirname, path.join('..','templates','common','app','server','models')));
        this.env.options.appPath = path.join(process.cwd() , 'app' , 'server' , 'models');
        // placeholder for providing a different profile 
        this.connection = false;
        // we need to get what type of database they are using to determine
        this.appTemplate(
            databaseType,
            this.name,
            '.model' + this.scriptSuffix
        );
    } catch(e) {
        this.log.error(e);
    }
};

// -- EOF --
